#include "hotel.h"

Hotel::Hotel() {

	this->_cadena = "";
	this->_habitaciones = Lista<Habitacion>();
	this->_nombre = "";
	this->_precioAccesorio = Lista<pair<Accesorio, Dinero> >();
	this->_tarifaXDia = Lista<pair<TipoHabitacion, Dinero> >();
    this->_reservas = Lista<Reserva>();
    this->_ingresos = Lista<pair<CheckIn, Habitacion> >();
    this->_salidas = Lista<CheckOut>();
}

Hotel::Hotel(const Nombre n, const Cadena c, const Lista<Habitacion>& hs,
	  const Lista<pair<TipoHabitacion, Dinero> > thxd, const Lista<pair<Accesorio, Dinero> > pa) {

	this->_cadena = c;
	this->_habitaciones = hs;
	this->_nombre = n;
	this->_precioAccesorio = pa;
	this->_tarifaXDia = thxd;
    this->_reservas = Lista<Reserva>();
    this->_ingresos = Lista<pair<CheckIn, Habitacion> >();
    this->_salidas = Lista<CheckOut>();
}

Nombre Hotel::nombre() const {

	return this->_nombre;
}

Cadena Hotel::cadena() const {

	return this->_cadena;
}

Lista<DNI> Hotel::huespedes() const {

    Lista<DNI> huespedes;
    int i = 0;
    while (i < _ingresos.longitud()) {

    	pair<CheckIn,Habitacion> ingreso = _ingresos.iesimo(i);
    	DNI dni = ingreso.first.first;

    	if (!salio(ingreso)) {

    		huespedes.agregar(dni);
    	}

    	i++;
    }

    return huespedes;
}

Lista<Habitacion> Hotel::habitaciones() const {

	return this->_habitaciones;
}

Lista<pair<CheckIn,Habitacion> > Hotel::ingresos() const {

	return _ingresos;
}

Lista<CheckOut> Hotel::salidas() const {

	return _salidas;
}

Lista<Reserva> Hotel::reservas() const {

	return _reservas;
}

Dinero Hotel::tarifaXDia(const TipoHabitacion t) const {

	int i = 0;
	while (i < _tarifaXDia.longitud()) {

		if (_tarifaXDia.iesimo(i).first == t)
			return _tarifaXDia.iesimo(i).second;

		i++;
	}

	return 0;
}

Dinero Hotel::precioAccesorio(const Accesorio a) const {

	int i = 0;
	while (i < _precioAccesorio.longitud()) {

		if (_precioAccesorio.iesimo(i).first == a)
			return _precioAccesorio.iesimo(i).second;

		i++;
	}

	return 0;
}

void Hotel::vender(const Cadena c) {

	_cadena = c;
}

void Hotel::hacerReserva(const Reserva& r) {

	_reservas.agregarAtras(r);
}

bool Hotel::sobreReservado(const Fecha f) const {

	return capacidad() < plazasReservadas(f);
}

void Hotel::registrarHuesped(const DNI d, const Fecha f, const Habitacion& a) {

	Lista<Reserva> reservas;
	bool existeReserva = false;
	int i = 0;
	while (i < _reservas.longitud()) {
		Reserva reserva = _reservas.iesimo(i);
		if (reserva.documento() == d && reserva.fechaDesde() == f && reserva.tipo() == a.tipo()) {
			existeReserva = true;
			reserva.confirmar();
			reservas.agregarAtras(reserva);
		} else {
			reservas.agregarAtras(reserva);
		}
		i++;
	}
	_reservas = reservas;
	if (existeReserva) {
		CheckIn checkIn;
		checkIn.first = d;
		checkIn.second = f;
		pair<CheckIn, Habitacion> ingreso;
		ingreso.first = checkIn;
		ingreso.second = a;
		_ingresos.agregar(ingreso);
	}
}

void Hotel::desRegistrarHuesped(const DNI d, const Fecha f) {

	CheckOut salida;
	salida.first = d;
	salida.second = f;
	_salidas.agregar(salida);
}

Lista<DNI> Hotel::huespedesConPalabra() const {
	Lista<DNI> huespedes;
	int i = 0;
	while (i < _ingresos.longitud()) {

		pair<CheckIn, Habitacion> ingreso = _ingresos.iesimo(i);
		if (salio(ingreso) && cumplioReserva(ingreso))
			huespedes.agregar(ingreso.first.first);

		i++;
	}

	return huespedes;
}

Dinero Hotel::calcularCuenta(const CheckIn i, const CheckOut o, const Habitacion& hb) const {

	return precioXAccesorios(hb) + tarifaXHabitacion(hb) * cantDias(i, o);
}

bool Hotel::reservasSolapadas(const DNI d) const {

	int j = 0;
	int i = 0;
	while (i < reservasDe(d).longitud()) {

	   while (j < reservasDe(d).longitud()) {

		   if (i != j
	    		  && seSolapan(reservasDe(d).iesimo(i), reservasDe(d).iesimo(j)))
	    	  return true;

		   j++;
	   }
	   j = 0;
	   i++;
	}

	return false;
}

bool Hotel::operator==(const Hotel& ht) const {

	return this->_cadena == ht._cadena
			&& mismos(_habitaciones, ht._habitaciones)
			&& mismos(_ingresos, ht._ingresos)
			&& this->_nombre == ht._nombre
			&& mismos(this->_precioAccesorio, ht._precioAccesorio)
			&& mismos(this->_reservas, ht._reservas)
			&& mismos(this->_salidas, ht._salidas)
			&& mismos(this->_tarifaXDia, ht._tarifaXDia);

}

void Hotel::mostrar(std::ostream& os) const {

	os << (char) ENCABEZADO_ARCHIVO;
	os << "|" << _nombre << "|";
	os << "|" << _cadena << "|";
	mostrarHabitaciones(os);
	mostrarIngresos(os);
	mostrarSalidas(os);
	mostrarReservas(os);
	mostrarTarifas(os);
	mostrarAccesorios(os);
}

void Hotel::guardar(std::ostream& os) const {

	mostrar(os);
}

void Hotel::cargar (std::istream& is) {

	cargarNombre(is);
	cargarCadena(is);
	cargarHabitaciones(is);
	cargarIngresos(is);
	cargarSalidas(is);
	cargarReservas(is);
	cargarTarifas(is);
	cargarAccesorios(is);
}

/**********************
 * Funciones privadas *
 **********************/

template<class T> bool Hotel::mismos(const Lista<T>& l1, const Lista<T>& l2) const {

	int i = 0;
	while (i < l1.longitud()) {
		if (!l2.pertenece(l1.iesimo(i)))
			return false;

		i++;
	}

	return true;
}

bool Hotel::salio(pair<CheckIn,Habitacion>& ingreso) const {
    bool res=false;
	Fecha fechaIngreso = ingreso.first.second;
	DNI dniIngreso = ingreso.first.first;

	int i = 0;
	while (i < _salidas.longitud()) {

		CheckOut salida = _salidas.iesimo(i);
		DNI dniSalida = salida.first;
		Fecha fechaSalida = salida.second;

		res = (dniIngreso == dniSalida && fechaSalida > fechaIngreso) || res;
		i++;
	}
return res;
}

int Hotel::cantidad(const TipoHabitacion tipo) const {

	switch (tipo) {
	case Simple:
		return 1;
	case Doble:
		return 2;
	case Triple:
		return 3;
	case Cuadruple:
		return 4;
	}

	return 0; // para que no me tire un warning
}

int Hotel::capacidad() const {

	int capacidad = 0;
	int i = 0;
	while (i < _habitaciones.longitud()) {

		Habitacion habitacion = _habitaciones.iesimo(i);
		capacidad += cantidad(habitacion.tipo());
		i++;
	}

	return capacidad;
}

int Hotel::plazasReservadas(Fecha f) const {

	int plazasReservadas = 0;
	int i = 0;
	while (i < _reservas.longitud()) {

		Reserva reserva = _reservas.iesimo(i);
		if (reserva.fechaDesde() <= f && reserva.fechaHasta() >= f)
			plazasReservadas += cantidad(reserva.tipo());

		i++;
	}

	return plazasReservadas;
}

int Hotel::cantDias(const CheckIn i, const CheckOut o) const {

	Fecha fechaIn = i.second;
	Fecha fechaOut = o.second;

	return fechaOut - fechaIn;
}

Dinero Hotel::tarifaXHabitacion(const Habitacion& hb) const {

	int i = 0;
	while (i < _tarifaXDia.longitud()) {

		pair<TipoHabitacion, Dinero> tarifa = _tarifaXDia.iesimo(i);
		if (tarifa.first == hb.tipo())
			return tarifa.second;
		i++;
	}

	return 0; // no debería llegar a esta línea, pero lo pongo para que no me tire el Warning
}

Dinero Hotel::precioXAccesorios (const Habitacion& hb) const {

	Dinero precio = 0;
	int i = 0;
	while (i < _precioAccesorio.longitud()) {

		pair<Accesorio, Dinero> accesorio = _precioAccesorio.iesimo(i);
		if (hb.accesorios().pertenece(accesorio.first))
			precio += accesorio.second;

		i++;
	}

	return precio;
}

Fecha Hotel::getFechaSalida(DNI dni, Fecha fechaIngreso) const {

	Lista<CheckOut> salidas;
	int i = 0;
	while (i < _salidas.longitud()) {

		CheckOut salida = _salidas.iesimo(i);
		DNI dniSalida = salida.first;
		Fecha fechaSalida = salida.second;
		if (dni == dniSalida && fechaSalida > fechaIngreso)
			salidas.agregar(salida);

		i++;
	}

	CheckOut salida = salidaMasProxima(salidas);
	return salida.second;
}

bool Hotel::cumplioReserva(pair<CheckIn,Habitacion>& ingreso) const {

	DNI dniIngreso = ingreso.first.first;
	Fecha fechaIngreso = ingreso.first.second;
	Fecha fechaSalida = getFechaSalida(dniIngreso, fechaIngreso);

	int i = 0;
	bool res = false;
	while (i < _reservas.longitud()) {

		Reserva reserva = _reservas.iesimo(i);
		if (reserva.confirmada()
				&& reserva.documento() == dniIngreso
				&& reserva.fechaDesde() == fechaIngreso
				&& reserva.fechaHasta() == fechaSalida)
			res = true;
		i++;
	}

	return res;
}

CheckOut Hotel::salidaMasProxima(Lista<CheckOut>& salidas) const {

	CheckOut salida = salidas.iesimo(0);
	int i = 1;
	while (i < salidas.longitud()) {

		CheckOut salidaX = salidas.iesimo(i);
		if (salidaX.second < salida.second)
			salida = salidaX;

		i++;
	}

	return salida;
}


Lista<Reserva> Hotel::reservasDe(const DNI d) const {

	Lista<Reserva> res;
	int i=0;
	while (i<_reservas.longitud()) {

		if (_reservas.iesimo(i).documento() == d)
			res.agregarAtras(_reservas.iesimo(i));

		i++;
	}

	return res;
}

bool Hotel::seSolapan(Reserva r1, Reserva r2) const {

	return (r1.fechaDesde() <= r2.fechaDesde() && r2.fechaDesde() <= r1.fechaHasta())
			|| (r1.fechaDesde() <= r2.fechaHasta() && r2.fechaHasta() <= r1.fechaHasta());
}


void Hotel::mostrarSalidas(std::ostream& os) const {

	os << "[";
	int i = 0;
	while (i < _salidas.longitud()) {

		CheckOut salida = _salidas.iesimo(i);
		os << "(" << salida.first << ", " << salida.second << ")";

		if (i < _salidas.longitud() - 1)
			os << ", ";

		i++;
	}

	os << "]";
}


void Hotel::mostrarReservas(std::ostream& os) const {

	os << "[";
	int i = 0;
	while (i < _reservas.longitud()) {

		Reserva reserva = _reservas.iesimo(i);
		reserva.mostrar(os);
		i++;
	}

	os << "]";
}


void Hotel::mostrarTarifas(std::ostream& os) const {

	os << "[";
	int i = 0;
	while (i < _tarifaXDia.longitud()) {

		pair<TipoHabitacion, Dinero> tarifa = _tarifaXDia.iesimo(i);
		os << "(" << tarifa.first << ", " << tarifa.second << ")";

		if (i < _tarifaXDia.longitud() - 1)
			os << ", ";

		i++;
	}

	os << "]";
}


void Hotel::mostrarAccesorios(std::ostream& os) const {

	os << "[";
	int i = 0;
	while (i < _precioAccesorio.longitud()) {

		pair<Accesorio, Dinero> accesorio = _precioAccesorio.iesimo(i);
		os << "(" << accesorio.first << ", " << accesorio.second << ")";

		if (i < _precioAccesorio.longitud() - 1)
			os << ", ";

		i++;
	}

	os << "]";
}


void Hotel::mostrarIngresos(std::ostream& os) const {

	int i = 0;
	os << "[";
	while (i < _ingresos.longitud()) {

		pair<CheckIn, Habitacion> ingreso = _ingresos.iesimo(i);
		os << "(("
				<< ingreso.first.first
				<< ", "
				<< ingreso.first.second
				<< "), ("
				<< ingreso.second.numero()
				<< ")";

		if (i < _ingresos.longitud() - 1)
			os << ", ";

		i++;
	}

	os << "]";
}

void Hotel::mostrarHabitaciones(std::ostream& os) const {

	int i = 0;
	os << "[";
	while (i < _habitaciones.longitud()) {

		os << "(";
		Habitacion habitacion = _habitaciones.iesimo(i);
		habitacion.mostrar(os);
		os << ")";

		if (i < _habitaciones.longitud() - 1)
			os << ", ";

		i++;
	}

	os << "] ";
}

void Hotel::cargarNombre(std::istream& is) {

	char x;
	is >> x;
	is >> x;
	is >> x;
	while (x != '|') {
		_nombre += x;
		is >> x;
	}
}

void Hotel::cargarCadena(std::istream& is) {

	char x;
	is >> x; // '|'
	is >> x; // primer caracter de la cadena
	while (x != '|') {

		_cadena += x;
		is >> x;
	}
}

void Hotel::cargarHabitaciones(std::istream& is) {

	char x;
	is >> x; // '[';
	while (x != ']') {

		Habitacion habitacion;
		habitacion.cargar(is);
		is >> x;
	}
}

void Hotel::cargarIngresos(std::istream& is) {

	char x;
	int i;
	is >> x; // '['
	while (x != ']') {

		is >> x; // '('
		is >> x; // '('
		is >> i; // dni

		CheckIn checkIn;
		is >> checkIn.first;
		is >> x; // ','
		is >> checkIn.second;
		is >> x; // ')'
		is >> x; // ','

		int numHabitacion;
		is >> numHabitacion;

		pair<CheckIn, Habitacion> ingreso;
		ingreso.first = checkIn;
		ingreso.second = habitacionPorNumero(numHabitacion);

		_ingresos.agregar(ingreso);

		is >> x;
	}
}

Habitacion Hotel::habitacionPorNumero(int numero) const {

	int i = 0;
	while (i < _habitaciones.longitud()) {

		if (_habitaciones.iesimo(i).numero() == numero)
			return _habitaciones.iesimo(i);

		i++;
	}
}

void Hotel::cargarSalidas(std::istream& is) {

	char x;
	int i;
	is >> x; // '['
	while (x != ']') {

		is >> x; // '('
		is >> i; // dni

		CheckOut checkOut;
		is >> checkOut.first;
		is >> x; // ','
		is >> checkOut.second;
		is >> x; // ')'

		_salidas.agregar(checkOut);
		is >> x;
	}
}

void Hotel::cargarReservas(std::istream& is) {

	char x;
	is >> x; // '[';
	while (x != ']') {

		Reserva reserva;
		reserva.cargar(is);
		is >> x;
	}
}

void Hotel::cargarTarifas(std::istream& is) {

	char x;
	is >> x; // '['
	while (x != ']') {

		string tipoHabitacion = "";
		Dinero precio;

		is >> x; // '('
		is >> x; // |
		getline(is, tipoHabitacion, '|'); // lee hasta |

		is >> x; // ,
		is >> precio;
		is >> x; // ')'
		is >> x;

		pair<TipoHabitacion, Dinero> tarifa;
		tarifa.first = tipoDeHabitacion(tipoHabitacion);
		tarifa.second = precio;
		_tarifaXDia.agregar(tarifa);
	}
}

Accesorio Hotel::getAccesorio(string acc) const {

	if (acc == "Jacuzzi") return Jacuzzi;
	else if (acc == "LCD") return LCD;
	else if (acc == "PS3") return PS3;
	else if (acc == "Pelotero") return Pelotero;
	else if (acc == "DVD") return DVD;
	else return Inflable;
}

TipoHabitacion Hotel::tipoDeHabitacion(string tipo) const {

	if (tipo == "Simple") return Simple;
	else if (tipo == "Doble") return Doble;
	else if (tipo == "Triple") return Triple;
	else return Cuadruple;
}

void Hotel::cargarAccesorios(std::istream& is) {

	char x;
	is >> x; // '['
	while (x != ']') {

		string accesorio = "";
		Dinero precio;

		is >> x; // '('
		is >> x; // |
		getline(is, accesorio, '|'); // lee hasta |

		is >> x; // ,
		is >> precio;
		is >> x; // ')'
		is >> x;

		pair<Accesorio, Dinero> acc;
		acc.first = getAccesorio(accesorio);
		acc.second = precio;
		_precioAccesorio.agregar(acc);
	}
}
